/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package Rewriting;

import RPS.EquivalenceMapping;
import RPS.*;
import RPS.MappingAssertion;
import RPS.MappingVariableRenamer;
import SPARQL.Constant;
import SPARQL.Obje;
import SPARQL.Pred;
import SPARQL.SPARQL;
import SPARQL.Subj;
import SPARQL.Term;
import SPARQL.TriplePattern;
import SPARQL.Utilities.QueryRenamer;
import SPARQL.Variable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 *
 * @author Mirko
 */
public class SPARQLRewriter {
    
    private RPS peerSystem;
    
    public SPARQLRewriter (RPS peerSystem) {
        this.peerSystem = peerSystem;
        
    }
    
    public Set<SPARQL> rewrite (SPARQL query){
        VariableNameGenerator varGenerator = VariableNameGenerator.getInstance();
        Set<SPARQL> queryList = new HashSet<SPARQL>();
        queryList.add(query);
        boolean addedQuery = true;
        while(addedQuery){
            addedQuery = false;
            Set<SPARQL> queryListIteration = new HashSet<SPARQL>();
            for (SPARQL queryToCopy: queryList){
                queryListIteration.add((SPARQL) queryToCopy.clone());
                
            }
            Set<MappingAssertion> totalMappings = new HashSet<MappingAssertion>();
            totalMappings.addAll(this.peerSystem.getMappings());
            //for (EquivalenceMapping equiMapping : this.peerSystem.getEquiMappings()){
                
            //    totalMappings.addAll(equiMapping.getRealatedGmappings());
            //}
            
            
            
            for (SPARQL queryIteration : queryListIteration){
                
                for (MappingAssertion mapping : totalMappings){
                    MappingAssertion mappingCopy = (MappingAssertion) mapping.clone();
                    MappingVariableRenamer renamer = new MappingVariableRenamer(mappingCopy);
                    renamer.renameVariables();
                    mappingCopy = renamer.getMapping();
                    Set<Set<TriplePattern>> triplesSet;
                    triplesSet = SetUtils.powerSet(((SPARQL)queryIteration.clone()).getTriplePatterns());
                    for (Set<TriplePattern> triples : triplesSet){
                        if (triples.size()!=0){
                            Set<TriplePattern> triplesClone = (Set<TriplePattern>) ((HashSet<TriplePattern>) triples).clone();
                            triplesClone.add(mappingCopy.getHead().getTriplePatterns().iterator().next());
                            TripleUnifier unifier = new TripleUnifier(triplesClone);
                            if (unifier.unify()){
                                SPARQL rewrittenQuery;
                                rewrittenQuery = rewriteGMapping(queryIteration , triples , mappingCopy, unifier);
                                
                                boolean addedIteration = true;
                                SPARQLEquivalenceChecker equiChecker = new SPARQLEquivalenceChecker();
                                for (SPARQL queryCheck : queryList) {
                                    addedIteration = addedIteration && !equiChecker.areEquivalent(queryCheck, rewrittenQuery);
                                }
                                
                                if (addedIteration) {
                                    queryList.add(rewrittenQuery);
                                    //togliere questa linea
                                    //System.out.println("Added query: " + rewrittenQuery);
                                }
                                
                                addedQuery = addedQuery || addedIteration;
                                
                                
                            }
                            
                            
                        }
                    }
                }
                Set<MappingAssertion> totalMappingsEquv = new HashSet<MappingAssertion>();
                for (EquivalenceMapping equiMapping : this.peerSystem.getEquiMappings()){
                
                totalMappingsEquv.addAll(equiMapping.getRealatedGmappings());
            }
                
                
                for (MappingAssertion mapping : totalMappingsEquv){
                    MappingAssertion mappingCopy = (MappingAssertion) mapping.clone();
                    MappingVariableRenamer renamer = new MappingVariableRenamer(mappingCopy);
                    renamer.renameVariables();
                    mappingCopy = renamer.getMapping();
                    Set<Set<TriplePattern>> triplesSet;
                    triplesSet = SetUtils.powerSet(((SPARQL)queryIteration.clone()).getTriplePatterns());
                    Set<TriplePattern> queryTriplesPatterns = ((SPARQL)queryIteration.clone()).getTriplePatterns();
                    for (TriplePattern tripleQuery : queryTriplesPatterns){
                        if (tripleQuery!=null){
                            TriplePattern tripleQueryClone = (TriplePattern)  tripleQuery.clone();
                            
                            TripleUnifierEqualities unifier = new TripleUnifierEqualities(tripleQueryClone, mappingCopy.getHead().getTriplePatterns().iterator().next());
                            if (unifier.unify()){
                                SPARQL rewrittenQuery;
                                Set<TriplePattern> setTriplesforAtriple =  new HashSet<TriplePattern>();
                                setTriplesforAtriple.add(tripleQueryClone);  //queste due linee, questa e quella sopra, solo perche' il metodo sotto accetta set of TriplePattern
                                rewrittenQuery = rewriteGMapping(queryIteration , setTriplesforAtriple , mappingCopy, unifier);
                                
                                boolean addedIteration = true;
                                SPARQLEquivalenceChecker equiChecker = new SPARQLEquivalenceChecker();
                                for (SPARQL queryCheck : queryList) {
                                    addedIteration = addedIteration && !equiChecker.areEquivalent(queryCheck, rewrittenQuery);
                                }
                                
                                if (addedIteration) {
                                    queryList.add(rewrittenQuery);
                                    //togliere questa linea
                                    //System.out.println("Added query: " + rewrittenQuery);
                                }
                                
                                addedQuery = addedQuery || addedIteration;
                                
                                
                            }
                            
                            
                        }
                    }
                }
                
                
                
            }
            
        }
        
        //System.out.println("prima delle query no nulls:" + queryList.size());
        Set<SPARQL> queryNoNulls = removeNullQueries(queryList);
        //System.out.println("dopo rimosso i nulls:" + queryNoNulls.size());
        for (SPARQL queryIRI : queryNoNulls) queryIRI.removeIRIinSelect();
        //System.out.println("dopo rimosso iri in select:" + queryNoNulls.size());
       Set<SPARQL> querySameSelect = setSameSelectVar(queryNoNulls, query);
        //System.out.println("dopo same select variables : "+ querySameSelect.size());
        return querySameSelect;
    }

    private SPARQL rewriteGMapping(SPARQL query, Set<TriplePattern> triples, MappingAssertion mapping, TripleUnifier unifier) {
        SPARQL rewrittenQuery = (SPARQL) query.clone();
       
        for (TriplePattern triplePattern : triples){
            rewrittenQuery.getTriplePatterns().remove(triplePattern);
        }
        Set<TriplePattern> bodyTriplePatterns = mapping.getBody().getTriplePatterns();
        for (TriplePattern triple : bodyTriplePatterns)
        rewrittenQuery.getTriplePatterns().add((TriplePattern) triple.clone());
        SPARQL rewrittenQueryClone = (SPARQL) rewrittenQuery.clone();
        for (TriplePattern triple : rewrittenQueryClone.getTriplePatterns()){
            rewrittenQuery.getTriplePatterns().remove(triple);
            triple.setS((Subj) ((Subj) unifier.unifFunction(triple.getS())).clone());
            triple.setP((Pred) ((Pred) unifier.unifFunction(triple.getP())).clone());
            triple.setO((Obje) ((Obje) unifier.unifFunction(triple.getO())).clone());
            rewrittenQuery.getTriplePatterns().add(triple);
        }
        
        for (Term variable : rewrittenQueryClone.getSelectVariables()){
            rewrittenQuery.getSelectVariables().remove(variable);
            // qui aggiunge riscrive la variabile secondo la funzione unifier
            Term rewrittenVar = (Term) unifier.unifFunction(variable);
            rewrittenQuery.getSelectVariables().add(rewrittenVar);
        }
        
        return rewrittenQuery;
        
    }

    private SPARQL rewriteEMapping(SPARQL query, Set<TriplePattern> triples, TriplePattern triple, TripleUnifier unifier) {
        SPARQL rewrittenQuery = (SPARQL) query.clone();
        for (TriplePattern triplePattern : triples){
            rewrittenQuery.getTriplePatterns().remove(triplePattern);
        }
        
        rewrittenQuery.getTriplePatterns().add((TriplePattern) triple.clone());
        SPARQL rewrittenQueryClone = (SPARQL) rewrittenQuery.clone();
        for (TriplePattern tripleIteration : rewrittenQueryClone.getTriplePatterns()){
            rewrittenQuery.getTriplePatterns().remove(tripleIteration);
            // qui riscrive la query secondo la funzione unifier (MGU)
            tripleIteration.setS((Subj) ((Subj) unifier.unifFunction(tripleIteration.getS())).clone());
            tripleIteration.setP((Pred) ((Pred) unifier.unifFunction(tripleIteration.getP())).clone());
            tripleIteration.setO((Obje) ((Obje) unifier.unifFunction(tripleIteration.getO())).clone());
            rewrittenQuery.getTriplePatterns().add(tripleIteration);
        }
        
        for (Term variable : rewrittenQueryClone.getSelectVariables()){
            rewrittenQuery.getSelectVariables().remove(variable);
            // riscrive le variabili secondo la funzione unifier
            Term rewritternVar = (Term) unifier.unifFunction(variable);
            rewrittenQuery.getSelectVariables().add(rewritternVar);
        }
        return rewrittenQuery;
    }

    private boolean isCorrect(SPARQL rewrittenQuery) {
        boolean isCorrect = true;
        
        for (TriplePattern triple : rewrittenQuery.getTriplePatterns())
            isCorrect &= isCorrect(triple);
        return isCorrect;
    }
    
    private boolean isCorrect (TriplePattern triple){
        boolean isCorrect = false;
        Set<Constant> constants = triple.getConstants();
         
            for (PeerSchema schema : this.peerSystem.getSchemas()){
                isCorrect |= schema.contains(constants);
                
            }
                
         return isCorrect;   
        
    }

    

    private Set<SPARQL> removeNullQueries(Set<SPARQL> queryList) {
    Set<SPARQL> querylistResult=  new HashSet<SPARQL>();
    for (SPARQL query : queryList)
        if (isCorrect(query))
            querylistResult.add(query);
    
    return querylistResult;
    }

    private Set<SPARQL> setSameSelectVar(Set<SPARQL> queryNoNulls, SPARQL originalQuery) {
        Set<SPARQL> setQuery = new HashSet<SPARQL>();
        for (SPARQL query : queryNoNulls) {
            setQuery.add(setSameSelectVar(query,originalQuery));
            
             
        }
        
        return setQuery;
    }

    private SPARQL setSameSelectVar(SPARQL query, SPARQL originalQuery) {
        
        
        QueryRenamer renamer = new QueryRenamer(query);
        Map<Variable, Variable> map = new HashMap<Variable, Variable> ();
            for (int i=0; i<originalQuery.getSelectVariables().size(); i++){
                map.put((Variable)query.getSelectVariables().get(i), (Variable)originalQuery.getSelectVariables().get(i));
                
                
            }
        renamer.renameVariables(map);
        return renamer.getQuery();
    }

    
   
}
